package com.black.knight.chess.domain;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import mediocre.board.Board;
import mediocre.board.Evaluation;
import mediocre.engine.Engine;
import mediocre.engine.Engine.LineEval;
import android.content.pm.ActivityInfo;
import android.view.Surface;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.ProgressBar;

import com.black.knight.chess.R;
import com.black.knight.chess.components.ChessClock;
import com.black.knight.chess.enums.NewGame;
import com.black.knight.chess.model.SahModel;
import com.black.knight.chess.model.SettingsModel;
import com.black.knight.chess.utils.ChessUtil;
import com.black.knight.chess.utils.Utils;

public class Game {
	private ArrayList<Move> moves = new ArrayList<Move>();
	
	private Player currentPlayer;
	private Player player1;
	private Player player2;
	private Player winner;
	
	private int selectedIndex = 0;
	
	private int gameDuration = 15;
	
 	private boolean playOnClock;
	
    private NewGame gameType;
    
    private boolean timeout;
    
    private int eval;
    
    public static int COMPUTER_TIME = 1;
    
	private Board board = new Board();
	
	private ExecutorService executorService = Executors.newCachedThreadPool();
	
	private String pgnGameString;
	
	private Integer mateIn;
    
	public Game() {
		this.selectedIndex = 0;
		this.winner = null;
		this.moves = new ArrayList<Move>();
		
		player1 = new Player("White");
		player2 = new Player("Black");
		player1.setWhitePlayer(true);
		player2.setWhitePlayer(false);
		
		currentPlayer = player1;
		setGameDuration(SettingsModel.getInstance().getGameDuration());
		setPlayOnClock(SettingsModel.getInstance().isPlayOnClock());
		
		board.setupStart();
	}
    
    public void togglePlayer() {
		if(currentPlayer == player1) {
			currentPlayer = player2;
		} else {
			currentPlayer = player1;
		}
	}
    
	public boolean isSahMat() {
		for(int i = 0; i<selectedIndex; i++) {
			if(moves.get(i).isCheckmate()) return true;
		}
		
		return false; 
	}
	
	public boolean isPat() {
		for(int i = 0; i<selectedIndex; i++) {
			if(moves.get(i).isPat()) return true;
		}
		
		return false; 
	}
	
	public boolean isDraw() {
		for(int i = 0; i<selectedIndex; i++) {
			if(moves.get(i).isDraw()) return true;
		}
		
		return false; 
	}
	
	public boolean isTimeout2() {
		for(int i = 0; i<selectedIndex; i++) {
			if(moves.get(i).isTimeout()) return true;
		}
		
		return false; 
	}
	
	public boolean isEndGame() {
		for(int i = 0; i<selectedIndex; i++) {
			if(moves.get(i).isEndGame()) return true;
		}
		
		return false; 
	}
	
	public boolean isBlackRokadaValid(Move move) {
		for (int i = 0; i<selectedIndex; i++) {
			if(moves.get(i).isCastle() && Move.isFigureBlack(moves.get(i).getMovedFigure())) return false;
			else if(moves.get(i).getMovedFigure() == R.drawable.bk) return false;
			else if(moves.get(i).getMovedFigure() == R.drawable.br && (moves.get(i).getColumn() == 0 && move.getNewColumn() == 2 || moves.get(i).getColumn() == 7 && move.getNewColumn() == 6)) return false;
		}
		
		if(move.getNewColumn() == 2 && SahModel.getInstance().getFigure(0, 0) != R.drawable.br) return false;
		
		if(move.getNewColumn() == 6 && SahModel.getInstance().getFigure(0, 7) != R.drawable.br) return false;
		
		if(SahModel.getInstance().getFigure(0, 4) != R.drawable.bk) return false;
		
		return true;
	}
	
	public boolean isWhiteRokadaValid(Move move) {
		for (int i = 0; i<selectedIndex; i++) {
			if( moves.get(i).isCastle() && Move.isFigureWhite( moves.get(i).getMovedFigure())) return false;
			else if(moves.get(i).getMovedFigure() == R.drawable.wk) return false;
			else if(moves.get(i).getMovedFigure() == R.drawable.wr && (moves.get(i).getColumn() == 0 && move.getNewColumn() == 2 || moves.get(i).getColumn() == 7 && move.getNewColumn() == 6)) return false;
		}
		
		if(move.getNewColumn() == 2 && SahModel.getInstance().getFigure(7, 0) != R.drawable.wr) return false;
		
		if(move.getNewColumn() == 6 && SahModel.getInstance().getFigure(7, 7) != R.drawable.wr) return false;
		
		if(SahModel.getInstance().getFigure(7, 4) != R.drawable.wk) return false;
		
		return true;
	}
	
	public ArrayList<Integer> getDestroyedFigures(boolean white) {
		ArrayList<Integer> figures = new ArrayList<Integer>();
		
		for(int i = 0; i<selectedIndex; i++) {
			if(moves.get(i).getDestroyedFigure() != R.drawable.selector && Move.isFigureWhite(moves.get(i).getDestroyedFigure()) && white) {				
				figures.add(moves.get(i).getDestroyedFigure());
			} else if(moves.get(i).getDestroyedFigure() != R.drawable.selector && Move.isFigureBlack(moves.get(i).getDestroyedFigure()) && !white) {
				figures.add(moves.get(i).getDestroyedFigure());
			}
		}
		
		return figures; 
	}
	
	public void switchPlayers() {
		Player tmp = player1;
		player1 = player2;
		player2 = tmp;
	}
	
	public void timeOut(Player player) {
		if(player == player1) {
			winner = player2;
		} else if(player == player2){
			winner = player1;
		}
		
		this.timeout = true;
		
		Utils.displayGameFinishedMessage(SahModel.context, SahModel.context.getResources().getString(R.string.time_out) + " " + winner.getName() + " " + SahModel.context.getResources().getString(R.string.win));
	}
	
	public void finish(boolean winner) {
		if(winner) {
			Player w = player1;
			if(SahModel.getInstance().getGame().getCurrentPlayer() == player1) {
				w = player2;
			}
			
			this.winner = w;
		} else this.winner = null;
		
		SahModel.getInstance().getWhiteClock().stop();
		SahModel.getInstance().getBlackClock().stop();
	}
	
	public int getGameDuration() {
		return gameDuration;
	}

	public void setGameDuration(int gameDuration) {
		this.gameDuration = gameDuration;
		
		ChessClock.GAME_DURATION = gameDuration * 1000 * 60;
	}
	
	public boolean isWhitePlay() {
		return currentPlayer.isWhitePlayer();
	}
    
    public NewGame getGameType() {
		return gameType;
	}

	public void setGameType(NewGame gameType) {
		this.gameType = gameType;
	}
	
	public Player getCurrentPlayer() {
		return currentPlayer;
	}

	public void setCurrentPlayer(Player currentPlayer) {
		this.currentPlayer = currentPlayer;
	}
	
	public Player getPlayer1() {
		return player1;
	}

	public void setPlayer1(Player player1) {
		this.player1 = player1;
	}

	public Player getPlayer2() {
		return player2;
	}

	public void setPlayer2(Player player2) {
		this.player2 = player2;
	}

	public Player getWinner() {
		return winner;
	}

	public void setWinner(Player winner) {
		this.winner = winner;
	}
	
	public ArrayList<Move> getMoves() {
		return moves;
	}

	public void setMoves(ArrayList<Move> moves) {
		this.moves = moves;
	}
	
	public int getSelectedIndex() {
		return selectedIndex;
	}

	public void setSelectedIndex(int selectedIndex) {
		this.selectedIndex = selectedIndex;
	}

	public boolean isTimeout() {
		return timeout;
	}

	public void setTimeout(boolean timeout) {
		this.timeout = timeout;
	}

	public boolean isFinished() {
		return timeout || isSahMat() || isPat();
	}
	
	public void goToEnd() {
		for(int i = selectedIndex; i<moves.size() - 1; i++) {
			if(moves.get(i + 1).getResult().length() > 0) return;
			SahModel.getInstance().play(i, false);			
			selectedIndex++;
		}
	}
	
	public void goToStart() {
		if(selectedIndex > 0 && moves.get(selectedIndex - 1).getResult().length() > 0) selectedIndex--;
		for(int i = selectedIndex - 1; i>=0; i--) {
			SahModel.getInstance().play(i, true);			
		}
		
		selectedIndex = 0;
	}
	
	public String toPGNGame() {
		StringBuffer pgnMove = new StringBuffer();
		
		for(int i = 0; i<selectedIndex; i++) {
			if(i == 0) pgnMove.append((i / 2 + 1) + ". ");
			else if(i % 2 == 0) pgnMove.append(" " + (i / 2 + 1) + ". ");
			else pgnMove.append(" ");
			
			if(moves.get(i).getPGNMove().length() == 0) {
				pgnMove.append(moves.get(i).getResult());
			} else {
				pgnMove.append(moves.get(i).getPGNMove());
			}
		}
		
		return pgnMove.toString();
	}
	
	Map<String, String> map = new LinkedHashMap<String, String>();
	
	public Map<String, String> getTags() {
		Map<String, String> map = new LinkedHashMap<String, String>();
		
		map.put("Game", "New game");
		
		if(player1.isWhitePlayer()) {
	    	map.put("White", player1.getName());
	    	map.put("Black", player2.getName());
		} else {
	    	map.put("White", player2.getName());
	    	map.put("Black", player1.getName());	        		
		}
		
		if(getResult().length() > 0) {
			map.put("Result", getResult());
		}
		
		return map;
	}
	
	public String getResult() {
		if(moves.size() == 0) return "";
		return moves.get(moves.size() - 1).getResult();
	}
	
	public String getFEN() {
		StringBuffer fenBuffer = new StringBuffer();
		
		int counter = 0;
		for(int i = 0; i<=7; i++) {
			for(int j = 0; j<=7; j++) {
				int figure = SahModel.getInstance().getFigure(i, j);
				String flag = ChessUtil.getFigureFANFlag(figure);
				
				if(flag.length() == 0) {
					counter++;
				}
				
				if(j == 7) {
					if(flag.length() == 0) {
						fenBuffer.append(counter);
					} else {
						if(counter > 0) fenBuffer.append(counter);
						fenBuffer.append(flag);
					}
					
					fenBuffer.append("/");
					
					counter = 0;
					continue;
				}
				
				if(flag.length() == 0) {
					continue;
				}
				
				if(counter > 0) {
					fenBuffer.append(counter);
					counter = 0;
				}
				
				fenBuffer.append(flag);
			}
		}
		
		fenBuffer.delete(fenBuffer.length() - 1, fenBuffer.length());
		
		String fenPlayer = isWhitePlay() ? "w" : "b";
		
		String fenCastleWK = "";
		String fenCastleWQ = "";
		String fenCastleBK = "";
		String fenCastleBQ = "";
		
		String halfMove = String.valueOf(getHalfMove());
		String fullMove = String.valueOf((selectedIndex / 2) + 1);
		
		Move moveK = new Move(0);
		moveK.setNewColumn(6);
		
		Move moveQ = new Move(0);
		moveQ.setNewColumn(2);
		
		if(isWhiteRokadaValid(moveK)) fenCastleWK = "K";
		if(isWhiteRokadaValid(moveQ)) fenCastleWQ = "Q";
		if(isBlackRokadaValid(moveK)) fenCastleBK = "k";
		if(isBlackRokadaValid(moveQ)) fenCastleBQ = "q";
		
		String fenCastle = fenCastleWK + fenCastleWQ + fenCastleBK + fenCastleBQ;
		
		if(fenCastle.length() == 0) fenCastle = "-";
		
		String fenEnPassant = "";
		
		if(selectedIndex > 0) {
			Move lastMove = moves.get(selectedIndex - 1);
			if(lastMove.getMovedFigure() == R.drawable.bp || lastMove.getMovedFigure() == R.drawable.wp) {
				if(lastMove.getNewRow() - lastMove.getRow() == 2) {
					fenEnPassant = ChessUtil.getChessCoords(lastMove.getNewColumn(), lastMove.getRow() + 1);
				} else if(lastMove.getNewRow() - lastMove.getRow() == -2) {
					fenEnPassant = ChessUtil.getChessCoords(lastMove.getNewColumn(), lastMove.getRow() - 1);
				}
			}
		}
		
		if(fenEnPassant.length() == 0) fenEnPassant = "-";
		
		fenBuffer.append(" ").append(fenPlayer).append(" ").append(fenCastle).append(" ").append(fenEnPassant).append(" ").append(halfMove).append(" ").append(fullMove);
		
		return fenBuffer.toString();
	}
	
	public void refreshBoard() {
		board.inputFen(getFEN());
	}
	
	public void calculateEval() {
		try {
			setEval(Evaluation.evaluate(board));
		} catch (Exception e) {
			if(SahModel.context.getEvalTextView() != null) {
				SahModel.context.getEvalTextView().setText(SahModel.context.getResources().getString(R.string.evaluation) + ": ---" );
			}
		}
	}

	public int getEval() {
		return eval;
	}

	public void setEval(int eval) {
		if(SahModel.context.getEvalTextView() != null) {
			SahModel.context.getEvalTextView().setText(SahModel.context.getResources().getString(R.string.evaluation) + ": " + eval);
		}
		this.eval = eval;
	}
	
	public boolean playComputer() {
		final GridView gridview = (GridView) SahModel.context.findViewById(R.id.gridview);
		final ImageButton buttonPrevious = (ImageButton) SahModel.context.findViewById(R.id.buttonPrevious);
		final ImageButton buttonNext = (ImageButton) SahModel.context.findViewById(R.id.buttonNext);
		
		gridview.setEnabled(false);
		buttonPrevious.setVisibility(ImageButton.INVISIBLE);
		buttonNext.setVisibility(ImageButton.INVISIBLE);
		
		final ProgressBar dialog = Utils.computerThinking(SahModel.context, player2.getName() + " " + SahModel.context.getResources().getString(R.string.thinking));
		
		int orientation = Utils.geOldtOrientation();
		
		if(orientation == Surface.ROTATION_0) {
			SahModel.context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		} else if(orientation == Surface.ROTATION_180) {
			SahModel.context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
		} else if(orientation == Surface.ROTATION_90) {
			SahModel.context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
		} else if(orientation == Surface.ROTATION_270) {
			SahModel.context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
		}
		
		executorService.execute(new Runnable() {
			@Override
			public void run() {
				try {
					LineEval eval = null;
					try {
						if(COMPUTER_TIME == 1) {
							eval = Engine.search(board, 1, 0, 0, 0, false);
						} else {
							eval = Engine.search(board, 0, 0, 0, COMPUTER_TIME, false);
						}
					} catch (Exception e) {
						SahModel.context.runOnUiThread(new Runnable() {
							@Override
							public void run() {
								if(dialog != null) dialog.setVisibility(ProgressBar.INVISIBLE);
					    		
								SahModel.context.hideAd();
								
								gridview.setEnabled(true);
								buttonPrevious.setVisibility(ImageButton.VISIBLE);
								buttonNext.setVisibility(ImageButton.VISIBLE);
								SahModel.context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
								Utils.displayAlertMessage(SahModel.context, SahModel.context.getResources().getString(R.string.unknown_error));
							}
						});
						
						return;
					}
					
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) { }
					
					final Move move = mediocre.board.Move.getMove(eval.line[0]);
					
					SahModel.context.runOnUiThread(new Runnable() {
						@Override
						public void run() {
							if(dialog != null) dialog.setVisibility(ProgressBar.INVISIBLE);
							SahModel.context.hideAd();
							gridview.setEnabled(true);
							buttonPrevious.setVisibility(ImageButton.VISIBLE);
							buttonNext.setVisibility(ImageButton.VISIBLE);
							ChessUtil.playMove(SahModel.context, move);
							SahModel.context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
						}
					});
				} catch (Exception e) {
					if(dialog != null) dialog.setVisibility(ProgressBar.INVISIBLE);
					SahModel.context.hideAd();
					gridview.setEnabled(true);
					buttonPrevious.setVisibility(ImageButton.VISIBLE);
					buttonNext.setVisibility(ImageButton.VISIBLE);
					SahModel.context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
				}
			}
		});
		
		return true;
	}
	
	public void recommendMove() {
		executorService.execute(new Runnable() {
			@Override
			public void run() {
				try {
					LineEval eval = Engine.search(board, 0, 0, 0, 10, false);
					
					final Move move = mediocre.board.Move.getMove(eval.line[0]);
					
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) { }
					
					SahModel.context.runOnUiThread(new Runnable() {
						@Override
						public void run() {
							SahModel.context.markRecommendedMove(move);
						}
					});
				} catch (IOException e) { } catch (Exception e) { }
				
			}
		});
	}
	
	public int getHalfMove() {
		int counter = 0;
		
		for (int i = selectedIndex - 1; i >= 0; i--) {
			if(!moves.get(i).isCaptured() && !(moves.get(i).getMovedFigure() == R.drawable.bp || moves.get(i).getMovedFigure() == R.drawable.wp)) {
				counter++;
			} else return counter;
		}
		
		return counter;
	}

	public String getPgnGameString() {
		if(pgnGameString == null) return toPGNGame();
		return pgnGameString;
	}

	public void setPgnGameString(String pgnGameString) {
		this.pgnGameString = pgnGameString;
	}

	public boolean isPlayOnClock() {
		return playOnClock;
	}

	public void setPlayOnClock(boolean playOnClock) {
		this.playOnClock = playOnClock;
	}

	public boolean checkDraw() {		
		int wb = SahModel.getInstance().getFigureCount(R.drawable.wb);
		int wn = SahModel.getInstance().getFigureCount(R.drawable.wn);
		int wp = SahModel.getInstance().getFigureCount(R.drawable.wp);
		int wq = SahModel.getInstance().getFigureCount(R.drawable.wq);
		int wr = SahModel.getInstance().getFigureCount(R.drawable.wr);
		int bb = SahModel.getInstance().getFigureCount(R.drawable.bb);
		int bn = SahModel.getInstance().getFigureCount(R.drawable.bn);
		int bp = SahModel.getInstance().getFigureCount(R.drawable.bp);
		int bq = SahModel.getInstance().getFigureCount(R.drawable.bq);
		int br = SahModel.getInstance().getFigureCount(R.drawable.br);

		if(wp == 0 && wq == 0 && wr == 0 && bp == 0 && bq == 0 && br == 0) {
			if((wn + wb < 2 && bn + bb < 2) || (wn == 2 && wb == 0 && bn + bb == 0) || (bn == 2 && bb == 0 && wn + wb == 0)) {
				Utils.displayGameFinishedMessage(SahModel.context, SahModel.context.getResources().getString(R.string.draw_insufficient_material));
				
				return true;
			}
		}
		
		if(getHalfMove() > 50) {
			Utils.displayGameFinishedMessage(SahModel.context, SahModel.context.getResources().getString(R.string.draw_fifty_move_rule));
			
			return true;
		}
		
		if(checkThreefoldRepetition()) {
			Utils.displayGameFinishedMessage(SahModel.context, SahModel.context.getResources().getString(R.string.draw_threefold_repetition));
			
			return true;
		}
		
		return false;
	}
	
	private boolean checkThreefoldRepetition() {
		if(selectedIndex < 6) return false;
		
		for(int i = 0; i<3; i++) {
			if(getMoves().get(selectedIndex - i*2).getNewPosition() != getMoves().get(selectedIndex - i*2 - 2).getPosition() 
			|| getMoves().get(selectedIndex - i*2).getPosition() != getMoves().get(selectedIndex - i*2 - 2).getNewPosition()
			|| getMoves().get(selectedIndex - i*2).getMovedFigure() != getMoves().get(selectedIndex - i*2 - 2).getMovedFigure()) return false;
			
			if(i<2) if(getMoves().get(selectedIndex - i*2 - 1).getNewPosition() != getMoves().get(selectedIndex - i*2 - 3).getPosition() 
			|| getMoves().get(selectedIndex - i*2 - 1).getPosition() != getMoves().get(selectedIndex - i*2 - 3).getNewPosition()
			|| getMoves().get(selectedIndex - i*2 - 1).getMovedFigure() != getMoves().get(selectedIndex - i*2 - 3).getMovedFigure()) return false;
		}
		
		return true;
	}

	public Integer getMateIn() {
		return mateIn;
	}

	public void setMateIn(Integer mateIn) {
		this.mateIn = mateIn;
	}
	
	public Boolean isSolved() {
		if(isSahMat() && getMateIn() * 2 >= selectedIndex) {
			return true;
		} else if(getMateIn() * 2 <= selectedIndex || isEndGame()){
			return false;
		}
		
		return null;
	}
}
